/* eslint-disable camelcase */
/**
 * Copyright(c) Live2D Inc. All rights reserved.
 *
 * Use of this source code is governed by the Live2D Open Software license
 * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
 */

import { CubismFramework, Option } from '@framework/live2dcubismframework';

import LAppDefine from './lappdefine';
import { LAppLive2DManager } from './lapplive2dmanager';
import { LAppPal } from './lapppal';
import { LAppTextureManager } from './lapptexturemanager';
import { LAppView } from './lappview';
import { LAppMessageBox } from './lappmessagebox';

export let canvas: HTMLCanvasElement = null;
export let messageBox: HTMLDivElement = null;
export let s_instance: LAppDelegate = null;
export let gl: WebGLRenderingContext = null;
export let frameBuffer: WebGLFramebuffer = null;

export type CanvasSize = { width: number; height: number } | 'auto';


/**
 * アプリケーションクラス。
 * Cubism SDKの管理を行う。
 */
export class LAppDelegate {
    /**
     * クラスのインスタンス（シングルトン）を返す。
     * インスタンスが生成されていない場合は内部でインスタンスを生成する。
     *
     * @return クラスのインスタンス
     */
    public static getInstance(): LAppDelegate {
        if (s_instance == null) {
            s_instance = new LAppDelegate();
        }

        return s_instance;
    }

    /**
     * クラスのインスタンス（シングルトン）を解放する。
     */
    public static releaseInstance(): void {
        if (s_instance != null) {
            s_instance.release();
        }

        s_instance = null;
    }

    /**
     * APPに必要な物を初期化する。
     */
    public initialize(): boolean {
        // キャンバスの作成
        canvas = document.createElement('canvas');
        
        canvas.id = LAppDefine.CanvasId;
        canvas.style.position = 'fixed';
        canvas.style.bottom = '0';
        canvas.style.right = '0';
        canvas.style.zIndex = '9999';
        LAppDefine.Canvas = canvas;
        
        if (LAppDefine.CanvasSize === 'auto') {
            this._resizeCanvas();
        } else {
            canvas.width = LAppDefine.CanvasSize.width;
            canvas.height = LAppDefine.CanvasSize.height;
        }

        canvas.style.opacity = '0';
        canvas.style.transition = '.7s cubic-bezier(0.23, 1, 0.32, 1)';

        // glコンテキストを初期化
        // @ts-ignore
        gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');

        if (!gl) {
            console.log('Cannot initialize WebGL. This browser does not support.');
            gl = null;

            // gl初期化失敗
            return false;
        }

        // キャンバスを DOM に追加
        document.body.appendChild(canvas);

        // 初始化对话框
        LAppMessageBox.getInstance().initialize(canvas);

        if (!frameBuffer) {
            frameBuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
        }

        // 透過設定
        gl.enable(gl.BLEND);
        gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        const supportTouch: boolean = 'ontouchend' in canvas;

        if (supportTouch) {
            // タッチ関連コールバック関数登録
            canvas.ontouchstart = onTouchBegan;
            canvas.ontouchmove = onTouchMoved;
            canvas.ontouchend = onTouchEnded;
            canvas.ontouchcancel = onTouchCancel;
        } else {
            // マウス関連コールバック関数登録
            canvas.onmousedown = onClickBegan;
            canvas.onmousemove = onMouseMoved;
            canvas.onmouseup = onClickEnded;
        }

        // AppViewの初期化
        this._view.initialize();

        // Cubism SDKの初期化
        this.initializeCubism();

        return true;
    }

    /**
     * Resize canvas and re-initialize view.
     */
    public onResize(): void {
        this._resizeCanvas();
        this._view.initialize();
        this._view.initializeSprite();

        // キャンバスサイズを渡す
        const viewport: number[] = [0, 0, canvas.width, canvas.height];

        gl.viewport(viewport[0], viewport[1], viewport[2], viewport[3]);
    }

    /**
     * 解放する。
     */
    public release(): void {
        this._textureManager.release();
        this._textureManager = null;

        this._view.release();
        this._view = null;

        // リソースを解放
        LAppLive2DManager.releaseInstance();

        // Cubism SDKの解放
        CubismFramework.dispose();
    }

    /**
     * 実行処理。
     */
    public run(): void {
        // メインループ
        const loop = (): void => {
            // インスタンスの有無の確認
            if (s_instance == null) {
                return;
            }

            // 時間更新
            LAppPal.updateTime();

            // 画面の初期化
            gl.clearColor(...LAppDefine.BackgroundRGBA);

            // 深度テストを有効化
            gl.enable(gl.DEPTH_TEST);

            // 近くにある物体は、遠くにある物体を覆い隠す
            gl.depthFunc(gl.LEQUAL);

            // カラーバッファや深度バッファをクリアする
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

            gl.clearDepth(1.0);

            // 透過設定
            gl.enable(gl.BLEND);
            gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

            // 描画更新
            this._view.render();

            // ループのために再帰呼び出し
            requestAnimationFrame(loop);
        };
        loop();
    }

    /**
     * シェーダーを登録する。
     */
    public createShader(): WebGLProgram {
        // バーテックスシェーダーのコンパイル
        const vertexShaderId = gl.createShader(gl.VERTEX_SHADER);

        if (vertexShaderId == null) {
            LAppPal.printMessage('failed to create vertexShader');
            return null;
        }

        const vertexShader: string =
            'precision mediump float;' +
            'attribute vec3 position;' +
            'attribute vec2 uv;' +
            'varying vec2 vuv;' +
            'void main(void)' +
            '{' +
            '   gl_Position = vec4(position, 1.0);' +
            '   vuv = uv;' +
            '}';

        gl.shaderSource(vertexShaderId, vertexShader);
        gl.compileShader(vertexShaderId);

        // フラグメントシェーダのコンパイル
        const fragmentShaderId = gl.createShader(gl.FRAGMENT_SHADER);

        if (fragmentShaderId == null) {
            LAppPal.printMessage('failed to create fragmentShader');
            return null;
        }

        const fragmentShader: string =
            'precision mediump float;' +
            'varying vec2 vuv;' +
            'uniform sampler2D texture;' +
            'void main(void)' +
            '{' +
            '   gl_FragColor = texture2D(texture, vuv);' +
            '}';

        gl.shaderSource(fragmentShaderId, fragmentShader);
        gl.compileShader(fragmentShaderId);

        // プログラムオブジェクトの作成
        const programId = gl.createProgram();
        gl.attachShader(programId, vertexShaderId);
        gl.attachShader(programId, fragmentShaderId);

        gl.deleteShader(vertexShaderId);
        gl.deleteShader(fragmentShaderId);

        // リンク
        gl.linkProgram(programId);

        gl.useProgram(programId);

        return programId;
    }

    /**
     * View情報を取得する。
     */
    public getView(): LAppView {
        return this._view;
    }

    public getTextureManager(): LAppTextureManager {
        return this._textureManager;
    }

    /**
     * コンストラクタ
     */
    constructor() {
        this._captured = false;
        this._mouseX = 0.0;
        this._mouseY = 0.0;
        this._isEnd = false;

        this._cubismOption = new Option();
        this._view = new LAppView();
        this._textureManager = new LAppTextureManager();
    }

    /**
     * Cubism SDKの初期化
     */
    public initializeCubism(): void {
        // setup cubism
        this._cubismOption.logFunction = LAppPal.printMessage;
        this._cubismOption.loggingLevel = LAppDefine.CubismLoggingLevel;
        CubismFramework.startUp(this._cubismOption);

        // initialize cubism
        CubismFramework.initialize();

        // load model
        LAppLive2DManager.getInstance();

        LAppPal.updateTime();
    }

    /**
     * Resize the canvas to fill the screen.
     */
    private _resizeCanvas(): void {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
    }

    _cubismOption: Option; // Cubism SDK Option
    _view: LAppView; // View情報
    _captured: boolean; // クリックしているか
    _mouseX: number; // マウスX座標
    _mouseY: number; // マウスY座標
    _isEnd: boolean; // APP終了しているか
    _textureManager: LAppTextureManager; // テクスチャマネージャー
}

let expressionCount = 0;

let toggle = true;

/**
 * クリックしたときに呼ばれる。
 */
function onClickBegan(e: MouseEvent): void {
    const live2dModel = LAppDelegate.getInstance();
    if (!live2dModel._view) {
        LAppPal.printMessage('view notfound');
        return;
    }
    
    const manager = LAppLive2DManager.getInstance();
    const model = manager.model;
    
    model.setRandomExpression();
}

/**
 * マウスポインタが動いたら呼ばれる。
 */
function onMouseMoved(e: MouseEvent): void {
    const live2dModel = LAppDelegate.getInstance();

    if (!live2dModel._view) {
        LAppPal.printMessage('view notfound');
        return;
    }

    const rect = (e.target as Element).getBoundingClientRect();
    const posX: number = e.clientX - rect.left;
    const posY: number = e.clientY - rect.top;

    live2dModel._view.onTouchesMoved(posX, posY);
}

/**
 * クリックが終了したら呼ばれる。
 */
function onClickEnded(e: MouseEvent): void {    
    LAppDelegate.getInstance()._captured = false;
    if (!LAppDelegate.getInstance()._view) {
        LAppPal.printMessage('view notfound');
        return;
    }

    const rect = (e.target as Element).getBoundingClientRect();
    const posX: number = e.clientX - rect.left;
    const posY: number = e.clientY - rect.top;

    LAppDelegate.getInstance()._view.onTouchesEnded(posX, posY);
}

/**
 * タッチしたときに呼ばれる。
 */
function onTouchBegan(e: TouchEvent): void {
    LAppPal.printMessage('touch event happens');
    const live2dModel = LAppDelegate.getInstance();
    if (!live2dModel._view) {
        LAppPal.printMessage('view notfound');
        return;
    }

    const posX = e.changedTouches[0].pageX;
    const posY = e.changedTouches[0].pageY;

    live2dModel._view.onTouchesBegan(posX, posY);
}

/**
 * スワイプすると呼ばれる。
 */
function onTouchMoved(e: TouchEvent): void {
    if (!LAppDelegate.getInstance()._captured) {
        return;
    }

    if (!LAppDelegate.getInstance()._view) {
        LAppPal.printMessage('view notfound');
        return;
    }

    const rect = (e.target as Element).getBoundingClientRect();

    const posX = e.changedTouches[0].clientX - rect.left;
    const posY = e.changedTouches[0].clientY - rect.top;

    LAppDelegate.getInstance()._view.onTouchesMoved(posX, posY);
}

/**
 * タッチが終了したら呼ばれる。
 */
function onTouchEnded(e: TouchEvent): void {
    LAppDelegate.getInstance()._captured = false;

    if (!LAppDelegate.getInstance()._view) {
        LAppPal.printMessage('view notfound');
        return;
    }

    const rect = (e.target as Element).getBoundingClientRect();

    const posX = e.changedTouches[0].clientX - rect.left;
    const posY = e.changedTouches[0].clientY - rect.top;

    LAppDelegate.getInstance()._view.onTouchesEnded(posX, posY);
}

/**
 * タッチがキャンセルされると呼ばれる。
 */
function onTouchCancel(e: TouchEvent): void {
    LAppDelegate.getInstance()._captured = false;

    if (!LAppDelegate.getInstance()._view) {
        LAppPal.printMessage('view notfound');
        return;
    }

    const rect = (e.target as Element).getBoundingClientRect();

    const posX = e.changedTouches[0].clientX - rect.left;
    const posY = e.changedTouches[0].clientY - rect.top;

    LAppDelegate.getInstance()._view.onTouchesEnded(posX, posY);
}
